#include "platform.h"
#include "stdio.h"

/* delay variable */
static __IO uint32_t fac_us;
static __IO uint32_t fac_ms;
#if 0
/* support printf function, usemicrolib is unnecessary */
#if (__ARMCC_VERSION > 6000000)
__asm (".global __use_no_semihosting\n\t");
void _sys_exit(int x)
{
    x = x;
}
/* __use_no_semihosting was requested, but _ttywrch was */
void _ttywrch(int ch)
{
    ch = ch;
}
FILE __stdout;
#else
#ifdef __CC_ARM
#pragma import(__use_no_semihosting)
struct __FILE
{
    int handle;
};
FILE __stdout;
void _sys_exit(int x)
{
    x = x;
}
/* __use_no_semihosting was requested, but _ttywrch was */
void _ttywrch(int ch)
{
    ch = ch;
}
#endif
#endif

#if defined (__GNUC__) && !defined (__clang__)
    #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
    #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif

/**
  * @brief  retargets the c library printf function to the usart.
  * @param  none
  * @retval none
  */
PUTCHAR_PROTOTYPE
{
    while(usart_flag_get(PRINT_UART, USART_TDBE_FLAG) == RESET);

    usart_data_transmit(PRINT_UART, (uint16_t)ch);

    while(usart_flag_get(PRINT_UART, USART_TDC_FLAG) == RESET);

    return ch;
}

#if (defined (__GNUC__) && !defined (__clang__)) || (defined (__ICCARM__))
#if defined (__GNUC__) && !defined (__clang__)
    int _write(int fd, char *pbuffer, int size)
#elif defined ( __ICCARM__ )
    #pragma module_name = "?__write"
    int __write(int fd, char *pbuffer, int size)
#endif
{
    for(int i = 0; i < size; i ++)
    {
        while(usart_flag_get(PRINT_UART, USART_TDBE_FLAG) == RESET);

        usart_data_transmit(PRINT_UART, (uint16_t)(*pbuffer++));

        while(usart_flag_get(PRINT_UART, USART_TDC_FLAG) == RESET);
    }

    return size;
}
#endif

#else

#include "stdio.h"
int fputc(int ch, FILE *f)
{
    while(usart_flag_get(PRINT_UART, USART_TDBE_FLAG) == RESET);

    usart_data_transmit(PRINT_UART, (uint16_t)ch);

    while(usart_flag_get(PRINT_UART, USART_TDC_FLAG) == RESET);

    return ch;
}

#endif

/**
  * @brief  initialize uart
  * @param  baudrate: uart baudrate
  * @retval none
  */
void uart_print_init(uint32_t baudrate)
{
    gpio_init_type gpio_init_struct;

    #if defined (__GNUC__) && !defined (__clang__)
    setvbuf(stdout, NULL, _IONBF, 0);
    #endif

    /* enable the uart and gpio clock */
    crm_periph_clock_enable(PRINT_UART_CRM_CLK, TRUE);
    crm_periph_clock_enable(PRINT_UART_TX_GPIO_CRM_CLK, TRUE);

    gpio_default_para_init(&gpio_init_struct);

    /* configure the uart tx pin */
    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init_struct.gpio_out_type  = GPIO_OUTPUT_PUSH_PULL;
    gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
    gpio_init_struct.gpio_pins = PRINT_UART_TX_PIN;
    gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
    gpio_init(PRINT_UART_TX_GPIO, &gpio_init_struct);

    gpio_pin_mux_config(PRINT_UART_TX_GPIO, PRINT_UART_TX_PIN_SOURCE, PRINT_UART_TX_PIN_MUX_NUM);

    /* configure uart param */
    usart_init(PRINT_UART, baudrate, USART_DATA_8BITS, USART_STOP_1_BIT);
    usart_transmitter_enable(PRINT_UART, TRUE);
    usart_enable(PRINT_UART, TRUE);
}

/**
  * @brief  initialize delay function
  * @param  none
  * @retval none
  */
void delay_init(void)
{
    /* configure systick */
    systick_clock_source_config(SYSTICK_CLOCK_SOURCE_AHBCLK_NODIV);
    fac_us = system_core_clock / (1000000U);
    fac_ms = fac_us * (1000U);
}

/**
  * @brief  inserts a delay time.
  * @param  nus: specifies the delay time length, in microsecond.
  * @retval none
  */
void delay_us(uint32_t nus)
{
    uint32_t temp = 0;
    SysTick->LOAD = (uint32_t)(nus * fac_us);
    SysTick->VAL = 0x00;
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ;

    do
    {
        temp = SysTick->CTRL;
    }
    while((temp & 0x01) && !(temp & (1 << 16)));

    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
    SysTick->VAL = 0x00;
}

/**
  * @brief  inserts a delay time.
  * @param  nms: specifies the delay time length, in milliseconds.
  * @retval none
  */
void delay_ms(uint16_t nms)
{
    uint32_t temp = 0;

    while(nms)
    {
        if(nms > STEP_DELAY_MS)
        {
            SysTick->LOAD = (uint32_t)(STEP_DELAY_MS * fac_ms);
            nms -= STEP_DELAY_MS;
        }
        else
        {
            SysTick->LOAD = (uint32_t)(nms * fac_ms);
            nms = 0;
        }

        SysTick->VAL = 0x00;
        SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;

        do
        {
            temp = SysTick->CTRL;
        }
        while((temp & 0x01) && !(temp & (1 << 16)));

        SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
        SysTick->VAL = 0x00;
    }
}

/**
  * @brief  inserts a delay time.
  * @param  sec: specifies the delay time, in seconds.
  * @retval none
  */
void delay_sec(uint16_t sec)
{
    uint16_t index;

    for(index = 0; index < sec; index++)
    {
        delay_ms(500);
        delay_ms(500);
    }
}

/**
  * @brief  usb 48M clock select
  * @param  clk_s:USB_CLK_HICK, USB_CLK_HEXT
  * @retval none
  */
void usb_clock48m_select(usb_clk48_s clk_s)
{
    if(clk_s == USB_CLK_HICK)
    {
        crm_usb_clock_source_select(CRM_USB_CLOCK_SOURCE_HICK);

        /* enable the acc calibration ready interrupt */
        crm_periph_clock_enable(CRM_ACC_PERIPH_CLOCK, TRUE);

        /* update the c1\c2\c3 value */
        acc_write_c1(7980);
        acc_write_c2(8000);
        acc_write_c3(8020);

        /* open acc calibration */
        acc_calibration_mode_enable(ACC_CAL_HICKTRIM, TRUE);
    }
    else
    {
        switch(system_core_clock)
        {
            /* 48MHz */
            case 48000000:
                crm_usb_clock_div_set(CRM_USB_DIV_1);
                break;

            /* 72MHz */
            case 72000000:
                crm_usb_clock_div_set(CRM_USB_DIV_1_5);
                break;

            /* 96MHz */
            case 96000000:
                crm_usb_clock_div_set(CRM_USB_DIV_2);
                break;

            /* 120MHz */
            case 120000000:
                crm_usb_clock_div_set(CRM_USB_DIV_2_5);
                break;

            /* 144MHz */
            case 144000000:
                crm_usb_clock_div_set(CRM_USB_DIV_3);
                break;

            /* 168MHz */
            case 168000000:
                crm_usb_clock_div_set(CRM_USB_DIV_3_5);
                break;

            /* 192MHz */
            case 192000000:
                crm_usb_clock_div_set(CRM_USB_DIV_4);
                break;

            /* 216MHz */
            case 216000000:
                crm_usb_clock_div_set(CRM_USB_DIV_4_5);
                break;

            /* 240MHz */
            case 240000000:
                crm_usb_clock_div_set(CRM_USB_DIV_5);
                break;

            /* 264MHz */
            case 264000000:
                crm_usb_clock_div_set(CRM_USB_DIV_5_5);
                break;

            /* 288MHz */
            case 288000000:
                crm_usb_clock_div_set(CRM_USB_DIV_6);
                break;

            default:
                break;

        }
    }
}

#include "usb_conf.h"
#include "usb_core.h"
#include "usbh_int.h"
#include "usbh_hid_class.h"
#include "usbh_user.h"

/* usb global struct define */
otg_core_type otg_core_struct;

/**
  * @brief  this function config gpio.
  * @param  none
  * @retval none
  */
void usb_config(void)
{
    gpio_init_type gpio_init_struct;

    crm_periph_clock_enable(OTG_PIN_GPIO_CLOCK, TRUE);
    gpio_default_para_init(&gpio_init_struct);

    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init_struct.gpio_out_type  = GPIO_OUTPUT_PUSH_PULL;
    gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
    gpio_init_struct.gpio_pull = GPIO_PULL_NONE;

    /* dp and dm */
    gpio_init_struct.gpio_pins = OTG_PIN_DP | OTG_PIN_DM;
    gpio_init(OTG_PIN_GPIO, &gpio_init_struct);

    gpio_pin_mux_config(OTG_PIN_GPIO, OTG_PIN_DP_SOURCE, OTG_PIN_MUX);
    gpio_pin_mux_config(OTG_PIN_GPIO, OTG_PIN_DM_SOURCE, OTG_PIN_MUX);

    #ifdef USB_SOF_OUTPUT_ENABLE
    crm_periph_clock_enable(OTG_PIN_SOF_GPIO_CLOCK, TRUE);
    gpio_init_struct.gpio_pins = OTG_PIN_SOF;
    gpio_init(OTG_PIN_SOF_GPIO, &gpio_init_struct);
    gpio_pin_mux_config(OTG_PIN_SOF_GPIO, OTG_PIN_SOF_SOURCE, OTG_PIN_MUX);
    #endif

    /* otgfs use vbus pin */
    #ifndef USB_VBUS_IGNORE
    gpio_init_struct.gpio_pins = OTG_PIN_VBUS;
    gpio_init_struct.gpio_pull = GPIO_PULL_DOWN;
    gpio_pin_mux_config(OTG_PIN_GPIO, OTG_PIN_VBUS_SOURCE, OTG_PIN_MUX);
    gpio_init(OTG_PIN_GPIO, &gpio_init_struct);
    #endif

    #ifdef USBH_5V_POWER_SWITCH
    crm_periph_clock_enable(OTG_PIN_POWER_SWITCH_CLOCK, TRUE);
    gpio_bits_set(OTG_PIN_POWER_SWITCH_GPIO, OTG_PIN_POWER_SWITCH);

    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
    gpio_init_struct.gpio_out_type  = GPIO_OUTPUT_OPEN_DRAIN;
    gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
    gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
    gpio_init_struct.gpio_pins = OTG_PIN_POWER_SWITCH;
    gpio_init(OTG_PIN_POWER_SWITCH_GPIO, &gpio_init_struct);
    #endif

    #ifdef USB_LOW_POWER_WAKUP
    usb_low_power_wakeup_config();
    #endif

    /* enable otgfs clock */
    crm_periph_clock_enable(OTG_CLOCK, TRUE);

    /* select usb 48m clcok source */
    usb_clock48m_select(USB_CLK_HEXT);

    /* enable otgfs irq */
    nvic_irq_enable(OTG_IRQ, 0, 0);

    /* init usb */
    usbh_init(&otg_core_struct,
              USB_FULL_SPEED_CORE_ID,
              USB_ID,
              &uhost_hid_class_handler,
              &usbh_user_handle);
}
#ifdef USB_LOW_POWER_WAKUP
/**
  * @brief  usb low power wakeup interrupt config
  * @param  none
  * @retval none
  */
void usb_low_power_wakeup_config(void)
{
    exint_init_type exint_init_struct;

    crm_periph_clock_enable(CRM_SCFG_PERIPH_CLOCK, TRUE);
    exint_default_para_init(&exint_init_struct);

    exint_init_struct.line_enable = TRUE;
    exint_init_struct.line_mode = EXINT_LINE_INTERRUPUT;
    exint_init_struct.line_select = OTG_WKUP_EXINT_LINE;
    exint_init_struct.line_polarity = EXINT_TRIGGER_RISING_EDGE;
    exint_init(&exint_init_struct);

    nvic_irq_enable(OTG_WKUP_IRQ, 0, 0);
}

/**
  * @brief  this function handles otgfs wakup interrupt.
  * @param  none
  * @retval none
  */
void OTG_WKUP_HANDLER(void)
{
    exint_flag_clear(OTG_WKUP_EXINT_LINE);
}

#endif

/**
  * @brief  this function handles otgfs interrupt.
  * @param  none
  * @retval none
  */
void OTG_IRQ_HANDLER(void)
{
    usbh_irq_handler(&otg_core_struct);
}

/**
  * @brief  usb delay millisecond function.
  * @param  ms: number of millisecond delay
  * @retval none
  */
void usb_delay_ms(uint32_t ms)
{
    /* user can define self delay function */
    delay_ms(ms);
}

/**
  * @brief  usb delay microsecond function.
  * @param  us: number of microsecond delay
  * @retval none
  */
void usb_delay_us(uint32_t us)
{
    delay_us(us);
}

/** @addtogroup 435_FLASH_write_read
  * @{
  */

#if defined (AT32F437xM) || defined (AT32F435xM) || defined (AT32F437xD) || defined (AT32F435xD)
    #define SECTOR_SIZE                      4096   /* this parameter depends on the specific model of the chip */
#else
    #define SECTOR_SIZE                      2048   /* this parameter depends on the specific model of the chip */
#endif

uint16_t flash_buf[SECTOR_SIZE / 2];

/**
  * @brief  read data using halfword mode
  * @param  read_addr: the address of reading
  * @param  p_buffer: the buffer of reading data
  * @param  num_read: the number of reading data
  * @retval none
  */
void flash_read(uint32_t read_addr, uint16_t *p_buffer, uint16_t num_read)
{
    uint16_t i;

    for(i = 0; i < num_read; i++)
    {
        p_buffer[i] = *(uint16_t*)(read_addr);
        read_addr += 2;
    }
}

/**
  * @brief  write data using halfword mode without checking
  * @param  write_addr: the address of writing
  * @param  p_buffer: the buffer of writing data
  * @param  num_write: the number of writing data
  * @retval result
  */
error_status flash_write_nocheck(uint32_t write_addr, uint16_t *p_buffer, uint16_t num_write)
{
    uint16_t i;
    flash_status_type status = FLASH_OPERATE_DONE;

    for(i = 0; i < num_write; i++)
    {
        status = flash_halfword_program(write_addr, p_buffer[i]);

        if(status != FLASH_OPERATE_DONE)
            return ERROR;

        write_addr += 2;
    }

    return SUCCESS;
}

/**
  * @brief  write data using halfword mode with checking
  * @param  write_addr: the address of writing
  * @param  p_buffer: the buffer of writing data
  * @param  num_write: the number of writing data
  * @retval result
  */
error_status flash_write(uint32_t write_addr, uint16_t *p_buffer, uint16_t num_write)
{
    uint32_t offset_addr;
    uint32_t sector_position;
    uint16_t sector_offset;
    uint16_t sector_remain;
    uint16_t i;
    flash_status_type status = FLASH_OPERATE_DONE;

    flash_unlock();
    offset_addr = write_addr - FLASH_BASE;
    sector_position = offset_addr / SECTOR_SIZE;
    sector_offset = (offset_addr % SECTOR_SIZE) / 2;
    sector_remain = SECTOR_SIZE / 2 - sector_offset;

    if(num_write <= sector_remain)
        sector_remain = num_write;

    while(1)
    {
        flash_read(sector_position * SECTOR_SIZE + FLASH_BASE, flash_buf, SECTOR_SIZE / 2);

        for(i = 0; i < sector_remain; i++)
        {
            if(flash_buf[sector_offset + i] != 0xFFFF)
                break;
        }

        if(i < sector_remain)
        {
            /* wait for operation to be completed */
            status = flash_operation_wait_for(ERASE_TIMEOUT);

            if((status == FLASH_PROGRAM_ERROR) || (status == FLASH_EPP_ERROR))
                flash_flag_clear(FLASH_PRGMERR_FLAG | FLASH_EPPERR_FLAG);
            else if(status == FLASH_OPERATE_TIMEOUT)
                return ERROR;

            status = flash_sector_erase(sector_position * SECTOR_SIZE + FLASH_BASE);

            if(status != FLASH_OPERATE_DONE)
                return ERROR;

            for(i = 0; i < sector_remain; i++)
            {
                flash_buf[i + sector_offset] = p_buffer[i];
            }

            if(flash_write_nocheck(sector_position * SECTOR_SIZE + FLASH_BASE, flash_buf, SECTOR_SIZE / 2) != SUCCESS)
                return ERROR;
        }
        else
        {
            if(flash_write_nocheck(write_addr, p_buffer, sector_remain) != SUCCESS)
                return ERROR;
        }

        if(num_write == sector_remain)
            break;
        else
        {
            sector_position++;
            sector_offset = 0;
            p_buffer += sector_remain;
            write_addr += (sector_remain * 2);
            num_write -= sector_remain;

            if(num_write > (SECTOR_SIZE / 2))
                sector_remain = SECTOR_SIZE / 2;
            else
                sector_remain = num_write;
        }
    }

    flash_lock();
    return SUCCESS;
}
